<template>
  <div :class="$style['dahua-player']" :id="el"></div>
</template>

<script>
import { ref, toRaw, watchEffect, onBeforeUnmount, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import DahuaPlayer from '@/utils/dahua.player'
import VueTypes from 'vue-types'

export default {
  name: 'dahua-video',
  props: {
    // [real: 实时预览, record: 录像回放]
    type: VueTypes.oneOf(['real', 'record']).def('real'),
    el: VueTypes.string.def('dahua-player'),
    pztEl: VueTypes.string.def(''),
    staticPath: VueTypes.string.def('/wsplayer-gov'),
    defaultWindowNumber: VueTypes.number.def(4),
    maxWindow: VueTypes.oneOf([1, 2, 3, 4, 6, 8, 9,  16, 25]).def(1),
    showControl: VueTypes.bool.def(true),
    operation: VueTypes.shape({
      streamChangeSelect: Boolean, // 主辅码流切换
      localRecordIcon: Boolean, // 本地录像功能按钮
      audioIcon: Boolean, // 开启关闭声音按钮
      snapshotIcon: Boolean, // 抓图按钮
      closeIcon: Boolean, // 关闭视频按钮
      extendIcons: VueTypes.any
    }).def({
      streamChangeSelect: true,
      localRecordIcon: true,
      audioIcon: true,
      snapshotIcon: true,
      closeIcon: true,
      extendIcons: null
    }).loose,
    channels: VueTypes.array.def([]),
    // 录像开始时间，单位为秒或者是完整的日期 [YYYY-MM-DD HH:mm:ss]
    startTime: VueTypes.oneOfType([String, Number, Date]).def(''),
    // 录像结束时间，单位为秒或者是完整的日期 [YYYY-MM-DD HH:mm:ss]
    endTime: VueTypes.oneOfType([String, Number, Date]).def(''),
    // 存储位置类型包括 [2:中心录像, 3:设备录像], 默认为 2
    recordSource: VueTypes.oneOfType([String, Number]).def(2)
  },
  emits: ['action', 'ptz:used', 'error'],
  setup (props, {emit}) {
    console.log('~~ create Dahua Player ~~')
    const useDahuaVideo = window.dahuaVideo
    const inited = ref(false)
    let currentWindowCount = 0
    let currentWindowMax = 1
    let currentChannelsSwitch = []
    const ptzUseMap = {} // <key:channelCode, value:是否可用云台>
    const ptzIndexMap = {} // <key:windowIndex, value:channelCode>
    const ptzMoveIndexMap = {} // <key:code, value:moveIndex>

    if (!useDahuaVideo) {
      return
    }

    let playerCls = null

    const baseOptions = {
      el: props.el,
      pztEl: props.pztEl,
      staticPath: props.staticPath,
      num: props.defaultWindowNumber,
      maxNum: 25,
      downloadMp4Record: true,
      showControl: props.showControl,  // 是否展示工具栏
      showRecordProgressBar: true, // 是否显示录像回放时间轴
      showIcons: Object.assign({}, props.operation),
      playerAction (type, data) {
        playerAction(type, data)
      }
    }

    /**
     * 播放器消息事件回调
     * @param {String} type 消息类型，包含以下类型:
     * initializationCompleted<type>: 动态加载解码库初始化完成
     * realSuccess<type>: 实时预览成功
     * realError<type>: 实时预览失败
     * recordSuccess<type>: 录像回放成功
     * recordError<type>: 录像回放失败
     * recordFinish<type>: 录像回放播放完成
     * statusChanged<type>:视频播放状态改变 data => {status:['closed'], windowIndex}
     * selectWindowChanged<type>: 选中的窗口发生改变 data => {channelId, playIndex}
     * windowNumChanged<type>: 播放器显示路数发生改变 data => number(更新后的窗口数量)
     * closeVideo<type>: 视频关闭 data => {changeVideoFlag:false,selectIndex:index,channelData{channelCode,id,isPause}}
     * throwCurrentRecordTime<type>: 抛出录像回放时间，对象含有窗口索引、Date 类型录像时间、通道编码 channelCode
     * switchStartTime<type>: 录像回放点击时间轴回调
     * @param {String|Object} data  消息的内容
     */
    const playerAction = (type, data) => {
      // console.log('🚀 ~ playerAction ~ playerCls:', playerCls)
      // console.log('🚀 ~ playerAction ~ type, data:', type, data)
      
      switch (type) {
          // 动态加载解码库初始化完成
          case 'initializationCompleted':
            inited.value = true
            break
          // 实时视频播放成功
          // {selectIndex, streamServerIp, rtspURL, channelId, channelData:{channelCode, isOnline, isPause, channelName, id, cameraType},streamType, wsList":["192.168.10.5:8555"],"wsURL":"ws://192.168.10.5:8555","playerAdapter":"stretching"}
          case 'realSuccess':
            if (ptzUseMap[data?.channelId]) {
              ptzIndexMap[data.selectIndex] = data.channelId
              
              console.log('🚀 ~ playerAction ~ ptzMoveIndexMap:', ptzMoveIndexMap)
              // 运控云台到视频预置点
              if (ptzMoveIndexMap[data.channelId] !== undefined) {
                playerCls.ptzMoveByIndex(data.channelId, ptzMoveIndexMap[data.channelId])
              }
            }
            
            break
          // 录像回放播放成功
          case 'recordSuccess':
            console.log('recordSuccess', data)
            if (data?.selectIndex !== undefined) {
              playerCls.play(data?.selectIndex)
            }
            break
          // 关闭视频
          // {selectIndex, channelData:{channelCode, isOnline, isPause, channelName, id, cameraType}}
          case 'closeVideo':
            // 移除云台是否可操作的配置
            delete ptzIndexMap[data.selectIndex]
            if (data?.channelData?.channelCode) {
              delete ptzUseMap[data.channelData.channelCode]
            }
            break
          // 当前窗口播放状态更新
          case 'statusChanged':
            switch (data?.status) {
              case 'playing':
                currentChannelsSwitch[data?.windowIndex] = 1

                break
              case 'closed':
                currentChannelsSwitch[data?.windowIndex] = 0

                break
              default:
                break
            }
            break
          // 选中的窗口发生改变 data => {channelId, playIndex}
          case 'selectWindowChanged':
            emit('ptz:used', ptzUseMap[data.channelId])
            break
          // 播放器显示路数发生改变 data => number(更新后的窗口数量)
          case 'windowNumChanged':
            currentWindowMax = data

            // 更新窗口的使用状态
            if (currentWindowMax > currentChannelsSwitch.length) {
              currentChannelsSwitch = [...currentChannelsSwitch, ...Array(currentWindowMax - currentChannelsSwitch.length).fill(0)]
            }

            if (currentWindowMax < currentChannelsSwitch.length) {
              currentChannelsSwitch = currentChannelsSwitch.slice(0, currentWindowMax)
            }

            playerCls.setSelectIndex(0)
            break
          // 异常 {errorCode:Number, errorInfo:String}
          case 'errorInfo':
            // console.error('err data from receiveMessageFromWSPlayer', data)
            // 1001 获取通道在线状态失败
            // 103 接口未转发到正确地址
            // 202 查询录像文件列表失败
            if (data.errorData && data.errorData.apiErrorInfo && data.errorData.apiErrorInfo.response && data.errorData.apiErrorInfo.response.status && data.errorData.apiErrorInfo.response.status > 400 && data.errorData.apiErrorInfo.response.status < 404) {
              ElMessage({
                type: 'error',
                message: '控制状态已过期，请刷新页面重试'
              })
              // window.dispatchEvent(new CustomEvent('unauthorized'))
              // playerCls.login()
            } else {
              // $.message({
              //     type: 'error',
              //     message: data.errorInfo || '错误信息提示'
              // })
              ElMessage({
                type: 'error',
                  message: data.errorInfo
              })
              console.log('error', data.errorCode, data.errorInfo)
              emit('error', data.errorCode, data.errorInfo)
            }
            break
          default:
            break
        }

      emit('action', type, data)
    }

    /**
     * 初始化播放器实例
     */
    const initPlayer = (options = {}) => {
      const _options = Object.assign({}, baseOptions, Object.assign({
        type: props.type
      }, options))

      playerCls.createPlayer(props.el, _options)
    }

    /**
     * 创建播放器
     */
    const createPlayer = (type, channels = [], autoPlay = true) => {
      const options = Object.assign({}, baseOptions, {
        type: type ?? props.type,
        num: Math.max(playerCls.fotmatChannelCount(channels.length), props.maxWindow | 0)
      })

      currentWindowCount = 0
      currentWindowMax = options.num

      // console.log('🚀 ~ createPlayer ~ currentWindowMax:', currentWindowMax)
      currentChannelsSwitch = Array(currentWindowMax).fill(0)

      if (!channels.length) {
        playerCls?.destroy()
        console.log('缺少需要播放的视频通道')

        return
      }

      // console.log('124 🚀 ~ createPlayer ~ options:', options)
      initPlayer(options)

      if (!autoPlay) {
        return
      }

      // console.log('🚀 ~ createPlayer ~ playerCls.selectIndex:', playerCls.selectIndex)

      switch (type) {
        case 'real':
          playRealVideo(channels, 2)

          break
        case 'record':
          playRecordVideo(channels, props.startTime, props.endTime, props.recordSource)

          break
        default:
          break
      }
    }

    /**
     * 获取可以添加的窗口 index
     * .eq 末尾追加时足够添加
     * .eq 按顺序添加时,末尾追加时不足添加数量，末尾添加一部分，前置替换一部分
     * 以下情况暂不处理
     * .eq 按顺序添加时,末尾追加时不足添加数量开始计算中间是否有关闭的情况
     * .eq 中间有关闭的情况下，关闭的窗口足够添加
     * .eq 中间有关闭的情况下，关闭的窗口和末尾的空位之和足够添加
     * .eq 中间有关闭的情况下，关闭的窗口和末尾的空位之和不足数量添加
     */
    const getAppendIndex = (count) => {
      // console.log('🚀 ~ getAppendIndex ~ count:', count)
      // console.log('currentChannelsSwitch:', currentChannelsSwitch)
      // 末尾可添加的 index
      let index = currentChannelsSwitch.findLastIndex(visible => !!visible)
      // 剩余可添加的数量
      let lessCount = currentChannelsSwitch.filter(visible => !visible).length
      // 可添加的数量
      const allowCount = Math.min(lessCount, count)
      // 末尾需要添加的数量
      let appendCount = 0
      // 中间需要添加的数量
      let middleCount = 0

      const result = {
        allowCount,
        middleIndex: -1,
        middleCount: 0,
        appendIndex: -1,
        appendCount: 0
      }

      // 全部为空的情况下
      if (index < 0) {
        result.appendIndex = 0
        result.appendCount = allowCount
        return result
      }

      // 末尾追加时足够添加
      if (index + 1 + allowCount <= currentWindowMax) {
        result.appendIndex = index + 1
        result.appendCount = allowCount
      } else {
        appendCount = currentWindowMax - index - 1
        middleCount = allowCount - appendCount

        if (appendCount > 0) {
          result.appendIndex = index + 1
          result.appendCount = appendCount
        }

        result.middleIndex = 0
        result.middleCount = middleCount
      }

      return result
    }

    const appendRealVideo = (channels, streamType = 2) => {
      // console.log('🚀 ~ appendRealVideo ~ channels:', channels)
      const appendIndex = getAppendIndex(channels.length)
      const appendChannels = channels.slice(0, appendIndex.appendCount)
      const middleChannels = channels.slice(appendIndex.appendCount, appendIndex.allowCount)
      // console.log('🚀 ~ appendRealVideo ~ appendIndex:', appendIndex)

      if (appendIndex.appendCount) {
        currentChannelsSwitch.splice(appendIndex.appendIndex, appendIndex.appendCount, ...(Array(appendIndex.appendCount).fill(1)))
        playRealVideo(appendChannels, streamType, appendIndex.appendIndex)
      }

      if (appendIndex.middleCount) {
        currentChannelsSwitch.splice(appendIndex.middleIndex, appendIndex.middleCount, ...(Array(appendIndex.middleCount).fill(1)))
        playRealVideo(middleChannels, streamType, appendIndex.middleIndex)
      }
    }

    const appendRecordVideo = (channels, startTime, endTime, recordSource = 2) => {
      const appendIndex = getAppendIndex(channels.length)
      const appendChannels = channels.slice(0, appendIndex.appendCount)
      const middleChannels = channels.slice(appendIndex.appendCount, appendIndex.allowCount)

      if (appendIndex.appendCount) {
        currentChannelsSwitch.splice(appendIndex.appendIndex, appendIndex.appendCount, ...(Array(appendIndex.appendCount).fill(1)))
        playRecordVideo(appendChannels, startTime, endTime, recordSource, appendIndex.appendIndex)
      }

      if (appendIndex.middleCount) {
        currentChannelsSwitch.splice(appendIndex.middleIndex, appendIndex.middleCount, ...(Array(appendIndex.middleCount).fill(1)))
        playRecordVideo(middleChannels, startTime, endTime, recordSource, appendIndex.middleIndex)
      }
    }

    /**
     * 播放实时视频
     * @param {Object[]} channels 
     * @param {String}   channels.channelCode  通道编码 code
     * @param {Boolean}  channels.isPause     是否暂停播放，默认为正常播放
     * @param {Number}   channels.channelName  频道名称
     * @param {Number}   [streamType=2]   码流类型 [1:主码流, 2:辅码流1, 3:辅码流2] 默认为 2:辅码流1
     * @param {Number}   [windowIndex] 加载视频的窗口 index
     */
    const playRealVideo = (channels, streamType = 2, windowIndex = -1) => {
      currentWindowCount += channels.length

      currentChannelsSwitch.splice(playerCls.selectIndex, channels.length, ...(Array(channels.length).fill(1)))

      playerCls.playRealVideo({
        channelList: channels.map((channel) => {
          if (channel.usePtz) {
            ptzUseMap[channel.channelCode] = true
          }

          // 记录云台的预置点
          if (channel.ptz !== undefined) {
            ptzMoveIndexMap[channel.channelCode] = channel.ptz
          }

          return {
            channelCode: channel.channelCode,
            isOnline: true,
            isPause: channel.isPause ?? false,
            channelName: channel.channelName || '通道名称'
          }
        }),
        streamType,
        windowIndex: windowIndex < 0 ? undefined : windowIndex
      })
    }

    /**
     * 播放回放录像
     * @param {Object[]}  channels 
     * @param {String}    channels.channelCode  通道编码 code
     * @param {Boolean}   [channels.isPause=true]  视频初始化是否暂停，默认暂停
     * @param {Timestamp}  startTime   录像开始时间，单位: 秒
     * @param {Timestamp}  endTime     录像结束时间，单位: 秒，如果 startTime 和 endTIme 不是同一天时，以有录像的第一天进行播放
     * @param {Number}    [recordSource=2] 存储位置类型包括 [2:中心录像, 3:设备录像], 默认为 2
     * @param {Number}    [windowIndex] 加载视频的窗口 index
     */
    const playRecordVideo = (channels, startTime, endTime, recordSource = 2, windowIndex = -1) => {
      console.log('🚀 ~ playRecordVideo ~ windowIndex:', channels, startTime, endTime, recordSource, windowIndex)
      // 把日期转化为时间戳，单位为秒
      if (typeof startTime === 'string') {
        startTime = new Date(startTime).getTime() / 1000
      }

      if (typeof endTime === 'string') {
        endTime = new Date(endTime).getTime() / 1000
      }

      if (windowIndex < 0) {
        playerCls.playRecordVideo({
          channelList: channels.map((chl) => {
            return {
              channelCode: chl.channelCode,
              isPause: chl.isPause ?? false
            }
          }),
          startTime,
          endTime,
          recordSource: recordSource | 0
        })

        return
      }

      currentChannelsSwitch.splice(playerCls.selectIndex, channels.length, ...(Array(channels.length).fill(1)))

      channels.map((channel, index) => {
        let start = channel.startTime ?? startTime
        let end = channel.endTime ?? endTime

        // 把日期转化为时间戳，单位为秒
        if (typeof start === 'string') {
          start = new Date(start).getTime() / 1000
        }

        if (typeof end === 'string') {
          end = new Date(end).getTime() / 1000
        }

        playerCls.playRecordVideo({
          channelList: [{
            channelCode: channel.channelCode,
            isPause: channel.isPause ?? false
          }],
          startTime: start,
          endTime: end,
          recordSource: recordSource | 0,
          windowIndex: windowIndex < 0 ? undefined : (windowIndex + index)
        })
      })
    }

    watchEffect(() => {
      if (!inited.value) {
        return
      }

      createPlayer(props.type, props.channels, true)
    })

    onMounted(() => {
      playerCls = new DahuaPlayer()

      // playerCls.login()
      initPlayer()
    })

    onBeforeUnmount(() => {
      playerCls.destroy()
      // playerCls.logout()
    })

    return {
      useDahuaVideo,
      createPlayer,
      playRealVideo,
      playRecordVideo,
      appendRealVideo,
      appendRecordVideo
    }
  }
}
</script>

<style lang="scss" module>
.dahua-player {
  width: 100%; height: 100%;
  overflow: hidden;
}
</style>
